Zhihao's Studio.

D3 on Angular

Word count: 1,327 / Reading time: 5 min
2014/11/15 Share

Angular与D3

前面写了几篇分别介绍AngularJS和D3的博客,他们各司其职,都在各自的一亩三分地里备受好评,那么,他们之间能否完成合作,实现共赢呢?这篇博客就来介绍如何将它们结合起来使用,使得我们的可视化能够更具有模块化方便复用具有交互性

Why Angular

使用Angular做可视化有很多原因,但其中最主要的原因还是directives(指令)。 指令使得做可复用的可视化更容易,可以使得我们做的可视化的时候就像使用内置的HTML那么简单,比如我需要一个热力图(后面写博客怎么做),那么我仅仅需要用\\就可以了,想想这种酸爽的滋味吧。即使你不知道热力图是怎么做的,但是你用directive就可以很容易的使用他。

另外,还有其他的原因,比如我们就可以使用Angular提供的其他内置的指令来工作了,比如使用ng-switch来使得在特定条件下才展示可视化图;如果我有一个数组,用ng-repeat指令可以绘制很多个图,这些都是Angular可以替你完成的。

最后一个优势是,AngularJS让建立associate更新更容易的。就像之前的associate brush那篇博客写的那样,没有AngularJS,我不得不找一个id来使得Radviz和scatterplot matrix联系起来,不过有了AngularJS,我们只需要处理好scope里数据的更新即可。

大部分的web框架建议开发者使用MVC设计模式,在Angular中,大部分情况下,V和M是自动联系起来的,这种双向绑定就发生在scope里。只要改变scope里的变量,它的变化会被自动发送到DOM,因此Angular并不是严格意义上的MVC,而是Model,View,Whatever.

理解directives

指令通过给我们权限去建立自己的HTML tags来实现复用。在某种程度上,你已经在用指令了,比如\\就是一个指令,他们由几个UI元素的组合而成,不过通过html5展现给你的就是一个简单的API,我们不需要知道他们内部是如何实现的。但是我们可以用它在不同的情况下让用户来输入电子邮件,日期,数值等等。。
下面我们来完成一个自己的directive,功能让这个指定说hello world吧。先建立自己的模块myApp,然后在这个模块中建立指令helloWorld。
如果你熟悉面向对象编程,应该熟悉构造函数,在指令中,构造函数就是link函数,这个函数中包含了这个元素出现在HTML时,所有应该发生的事情。
restrict有ACME,这些都是Angular的基础,这里不多介绍了,这里我们使用E。完整的代码如下图所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// \<html\>
// \<head lang="en"\>
// \<meta charset="UTF-8"\>
// \<script src="./d3.js"\>\</script\>
// \<script src="./angular.js"\>\</script\>
// \<script src="./sc.js"\>
// var myApp = angular.module('myApp',[]());
//
// myApp.directive('helloWorld',function(){
//
// function link(scope,element,attr){
// element.text='hello world!';
// }
// return {
// link:link,
// restrict:'E'
// }
//
// })
//
// \</script\>
// \<title\>\</title\>
// \</head\>
// \<body ng-app="myApp"\>
// \<hello-world\>hello world!\</hello-world\>
//
//
// \</body\>
// \</html\>

reusable visualization directive

上面展示的例子虽然正确,但是却没什么用,这节就完成一个可复用的donut chart指令。首先用d3绘制一个,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// \<script\>
// var color = d3.scale.category10();
// var data =[10,20,30]();
// var width =300;
// var height = 300;
// var min = Math.min(width,height);
// var svg = d3.select('body').append('svg');
// var pie = d3.layout.pie().sort(null);
//
// var arc = d3.svg.arc().outerRadius(min/2*0.9).innerRadius(min/2*0.5);
//
// svg.attr({width:width,height:height});
//
// var g = svg.append('g')
// .attr('transform','translate('+width/2+','+height/2+')');
//
// g.selectAll('path').data(pie(data))
// .enter().append('path')
// .style('stroke','white')
// .attr('d',arc)
// .attr('fill',function(d,i){return color(i)})
//
//
// \</script\>

和上面的hello world一样,我们需要在module里设置新的指令,不同之处是将名字改成了donutChart了。在link函数里,我们将上面的d3代码全部拷贝进去,不过有些地方也得变一下,我们需要将d3.select(‘body’)改成d3.select(element0),从全局的dom改成了相对的。为什么不直接使用element呢?因为element是一个jQuery的selection,不是一个DOM元素。
如果顺利,将会得到下面的结果。

成功了,我们完成了可视化图的复用!

Isolate scope

但是聪明的你很快就发现了问题,我们的可视化图无论何时都是一样的,这可不行!我们希望可以自己传一些数据,比如第一个图是1,2,3,4;第二个图是2,3,4,5;这样才勉强可以。
为了达到这样的目的,我们需要用一个叫isolate scope的东西。它允许我们使用attribute传递数据到指令中。告诉Angular我们的指令需要有一个isolate scope的方法很简单,我们只需从指令声明的返回处增加一个scope属性即可。 在restrict下面增加一行scope:{data:’=},在link函数中,我们就可以直接用scope.data了。
当我们使用

1
2
3
4
5
6
\<bodyng-app="myApp"ng-init="ourSharedData=[8,2,9]()"\>
\<donut-chart data="ourSharedData"\>\</donut-chart\>
\<donut-chart data="ourSharedData"\>\</donut-chart\>
\<donut-chart data="ourSharedData"\>\</donut-chart\>

时候,确实达到了我们想要的效果。

未完待续…

CATALOG
  1. 1. Angular与D3
  2. 2. Why Angular
  3. 3. 理解directives
  4. 4. reusable visualization directive
  5. 5. Isolate scope